/**
 * Copyright &copy; 2016-2017 <a href="http://git.oschina.net/whoamien/backend_management">Backend Management</a> All rights reserved.
 */
package com.xw.framework.base.service;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaBuilder.In;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.beanutils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.transaction.annotation.Transactional;

import com.xw.framework.annotation.ColumnQuery;
import com.xw.framework.base.dao.BaseDAO;
import com.xw.framework.base.dao.BaseEntity;
import com.xw.framework.base.page.BaseQuery;
import com.xw.util.DateUtil;
import com.xw.util.ShiroSecurityUtil;
import com.xw.util.StringUtil;

/**
 * The Class BaseServiceImpl.
 *
 * @author Xi Wei
 * @param <E> the element type
 * @param <PK> the generic type
 */
@Transactional
public class BaseServiceImpl<E, PK extends Serializable> implements BaseService<E, PK> {

	/** The repository. */
	@Autowired
	protected BaseDAO<E, PK> repository;

	/* (non-Javadoc)
	 * @see com.xw.framework.base.service.BaseService#findAll()
	 */
	@Override
	public List<E> findAll() {
		return (List<E>)repository.findAll();
	}
	
	/* (non-Javadoc)
	 * @see com.xw.framework.base.service.BaseService#save(java.lang.Object)
	 */
	@Override
	public E save(E entity) {
		if(entity == null || !(entity  instanceof BaseEntity)){
			return null;
		}
		
		if(((BaseEntity)entity).getId() == null){
			try {
				BeanUtils.setProperty(entity, "updateUserId", ShiroSecurityUtil.getLoginId());
				BeanUtils.setProperty(entity, "updateDate", DateUtil.getSystemTimestamp());
				BeanUtils.setProperty(entity, "createUserId", ShiroSecurityUtil.getLoginId());
				BeanUtils.setProperty(entity, "createDate", DateUtil.getSystemTimestamp());
				BeanUtils.setProperty(entity, "systemId", ShiroSecurityUtil.getUserSessionInfo().getSystemId());
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else{
			try {
				BeanUtils.setProperty(entity, "updateUserId", ShiroSecurityUtil.getLoginId());
				BeanUtils.setProperty(entity, "updateDate", DateUtil.getSystemTimestamp());
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return repository.save(entity);
	}
	
	/* (non-Javadoc)
	 * @see com.xw.framework.base.service.BaseService#delete(java.io.Serializable)
	 */
	@Override
	public void delete(PK id){
		repository.delete(id);
	}

	private PageRequest bindPageRequest(BaseQuery query) {

		if (!StringUtil.isNullOrEmpty(query.getSortColumns())) {
			String[] sortColumns = query.getSortColumns().split(",");
			List<Order> orderList = new ArrayList<Order>();
			for (int i = 0; i < sortColumns.length; i++) {
				String[] sort = sortColumns[i].split(" ");
				if (sort.length == 2) {
					if (sort[1].equalsIgnoreCase("ASC")) {
						Order order = new Order(Direction.ASC, sort[0]);
						orderList.add(order);
					} else {
						Order order = new Order(Direction.DESC, sort[0]);
						orderList.add(order);
					}
				} else {
					Order order = new Order(Direction.ASC, sort[0]);
					orderList.add(order);
				}
			}
			if (orderList.isEmpty()) {
				return new PageRequest(query.getPage() - 1, query.getSize());
			} else {
				return new PageRequest(query.getPage() - 1, query.getSize(), new Sort(orderList));
			}

		} else {
			return new PageRequest(query.getPage() - 1, query.getSize());
		}
	}

	/* (non-Javadoc)
	 * @see com.xw.framework.base.service.BaseService#findPage(com.xw.framework.base.page.BaseQuery)
	 */
	@Override
	public Page<E> findPage(BaseQuery baseQuery) {
		BaseQuery clone = (BaseQuery) baseQuery.clone();

		Pageable pageable = bindPageRequest(baseQuery);
		return repository.findAll(new Specification<E>() {

			@Override
			public Predicate toPredicate(Root<E> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				List<Predicate> lstPredicates = new ArrayList<Predicate>();
				try {
					PropertyDescriptor[] pds;
					pds = Introspector.getBeanInfo(baseQuery.getClass()).getPropertyDescriptors();
					if (pds != null) {
						for (PropertyDescriptor pd : pds) {
							Method readMethod = pd.getReadMethod();
							if (readMethod == null) {
								continue;
							}
							ColumnQuery c = readMethod.getAnnotation(ColumnQuery.class);

							Object value = readMethod.invoke(baseQuery, new Object[] {});
							
							if(c != null){
								String column = c.name();
								if (column == null || column.equals("")) {
									column = pd.getName();
								}
								if("systemId".equals(pd.getName())){
									if(value == null){
										value = ShiroSecurityUtil.getSystemId();
									}
									if(value != null){
										if(c.addIsNull()){
											Predicate predicate = cb.isNull(getPath(root, column));
											if(value != null  && !"".equals(value))
											{
												lstPredicates.add(cb.or(predicate,cb.equal(getPath(root, column).as(String.class), value)));
											}
											else{
												lstPredicates.add(predicate);
											}
										}else{
											if(value != null  && !"".equals(value))
											{
												lstPredicates.add(cb.equal(getPath(root, column).as(String.class), value));
											}
										}
									}
									continue;
								}
								if (value != null && !"".equals(value)) {
									
									switch (c.operator()) {
									case LIKE:
										lstPredicates.add(cb.like(getPath(root, column).as(String.class), "%" + value + "%"));
										break;
									case IN:
										Iterator iterator = ((Collection) value).iterator();
										In in = cb.in(getPath(root, column));
										while (iterator.hasNext()) {
											in.value(iterator.next());
										}
										lstPredicates.add(in);
										break;
									case GREATERTHAN:
										if (Number.class.isAssignableFrom(pd.getPropertyType())) {
											lstPredicates.add(cb.gt(getPath(root, column).as(Number.class), (Number) value));
										} else if (Date.class.isAssignableFrom(pd.getPropertyType())) {
											lstPredicates.add(cb.greaterThan(getPath(root, column).as(Date.class), (Date) value));
										}
										break;
									case GREATERTHANEQUAL:
										if (Number.class.isAssignableFrom(pd.getPropertyType())) {
											lstPredicates.add(cb.ge(getPath(root, column).as(Number.class), (Number) value));
										} else if (Date.class.isAssignableFrom(pd.getPropertyType())) {
											lstPredicates.add(cb.greaterThanOrEqualTo(getPath(root, column).as(Date.class), (Date) value));
										}
										break;
									case LESSTHAN:
										if (Number.class.isAssignableFrom(pd.getPropertyType())) {
											lstPredicates.add(cb.lt(getPath(root, column).as(Number.class), (Number) value));
										} else if (Date.class.isAssignableFrom(pd.getPropertyType())) {
											lstPredicates.add(cb.lessThan(getPath(root, column).as(Date.class), (Date) value));
										}
										break;
									case LESSTHANEQUAL:
										if (Number.class.isAssignableFrom(pd.getPropertyType())) {
											lstPredicates.add(cb.le(getPath(root, column).as(Number.class), (Number) value));
										} else if (Date.class.isAssignableFrom(pd.getPropertyType())) {
											lstPredicates.add(cb.lessThanOrEqualTo(getPath(root, column).as(Date.class), (Date) value));
										}
										break;
									default:		
										if(value instanceof Boolean){
											lstPredicates.add(cb.equal(getPath(root, column).as(Integer.class), Boolean.TRUE.equals(value)? 1: 0));
										}else{
											lstPredicates.add(cb.equal(getPath(root, column).as(String.class), value));
										}
										
										
									}
								}
							}
							
							
						}
					}
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				Predicate[] arrayPredicates = new Predicate[lstPredicates.size()];
				
				return cb.and(lstPredicates.toArray(arrayPredicates));
			}

		}, pageable);

	}
	
	
	/* (non-Javadoc)
	 * @see com.xw.framework.base.service.BaseService#getById(java.io.Serializable)
	 */
	@Override
	public E getById(PK pk){
		return repository.findOne(pk);
	}
	
	private Path<E> getPath(Root<E> root,String column){
		String[] columns = column.split("\\.");
		if(columns.length <= 1){
			return root.get(column);
		}
		
		List<String> list = Arrays.asList(columns);
		
		
		Join join = root.join(columns[0]);
		for(int i = 1;i<columns.length - 1;i++){
			join = join.join(columns[i]);
		}
		return join.get(columns[columns.length - 1]);
	}

}
